// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <fbl/ref_ptr.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/time.h>
#include <zircon/types.h>

#include <dispatcher-pool/dispatcher-event-source.h>

namespace dispatcher {

// class Timer
//
// Timer is one of the EventSources in the dispatcher framework used to manage
// a zircon timer object.
//
// :: Handler ::
//
// Timer defines a single handler (ProcessHandler) which runs when the timer
// fires.  Returning an error from the process handler will cause the timer to
// automatically become deactivated.
//
// :: Activation ::
//
// Activation simply requires a user to provide a valid ExecutionDomain and a
// valid ProcessHandler.  The timer kernel object itself will be allocated
// internally.
//
// :: Arming/Canceling ::
//
// Arming the timer to fire at a specific point in time and canceling the timer
// are operations protected by an internal lock, they may be called from any
// thread.  Attempting to Arm a deactivated timer will result in an error.  The
// time at which a timer fires is always an absolute time given on the
// ZX_CLOCK_MONOTONIC timeline.
//
// After firing, a timer is always disarmed.  It may be armed, canceled and
// re-armed again any number of times from the dispatch operation within the
// context of the ExecutionDomain the timer was bound to.
//
class Timer : public EventSource {
 public:
  static constexpr size_t MAX_HANDLER_CAPTURE_SIZE = sizeof(void*) * 2;
  using ProcessHandler = fbl::InlineFunction<zx_status_t(Timer*), MAX_HANDLER_CAPTURE_SIZE>;

  static fbl::RefPtr<Timer> Create(zx_duration_t early_slop_nsec = 0);

  // Activate a timer object, creating the kernel timer and binding the Timer
  // object to an execution domain and a processing handler.
  //
  // The operation will fail if the Timer has already been bound, or either
  // the domain reference or processing handler is invalid.
  //
  // |slack_type| specifies the type of slack that may be applied to the
  // deadline.
  //
  // TODO(ZX-3311): Remove the default for slack_type and pass the correct
  // value based on the needs of the callers.
  zx_status_t Activate(fbl::RefPtr<ExecutionDomain> domain, ProcessHandler process_handler,
                       uint32_t slack_type = ZX_TIMER_SLACK_LATE);
  virtual void Deactivate() __TA_EXCLUDES(obj_lock_) override;

  // Arm a timer object to fire at the |deadline| adjusted by the
  // |slack_amount| and the |slack_type| used to Activate.
  //
  // TODO(ZX-3311): Remove the default for slack_amount and pass the correct
  // value based on the needs of the callers.
  zx_status_t Arm(zx_time_t deadline, zx_duration_t slack_amount = 0);
  void Cancel();

 protected:
  void Dispatch(ExecutionDomain* domain) __TA_EXCLUDES(obj_lock_) override;

 private:
  friend class fbl::RefPtr<Timer>;

  Timer(zx_duration_t early_slop_nsec)
      : EventSource(ZX_TIMER_SIGNALED), early_slop_nsec_(early_slop_nsec) {}

  void DisarmLocked() __TA_REQUIRES(obj_lock_);
  zx_status_t SetTimerAndWaitLocked() __TA_REQUIRES(obj_lock_);

  const zx_duration_t early_slop_nsec_;
  bool armed_ __TA_GUARDED(obj_lock_) = false;
  bool timer_set_ __TA_GUARDED(obj_lock_) = false;
  zx_time_t deadline_ __TA_GUARDED(obj_lock_) = 0;
  zx_duration_t slack_amount_ __TA_GUARDED(obj_lock_) = 0;

  ProcessHandler process_handler_;
};

}  // namespace dispatcher
